<?php
/**
 * Service_Avtodispetcher
 *
 * Класс Service_Avtodispetcher осуществляет работу c сайтом avtodispetcher.ru
 * 
 */
class Service_Avtodispetcher {
	public $currentCities = array();		// Массив городов в БД на момент запуска скрипта
	public $gettedRoutes = 0;				// Счётчик от парсенный маршрутов
	public $routeParcingLimit = 999999;		// Лимит обращений к avtodispetcher.ru за время работу скрипта
	public $sleeptime = 11;					// Среднее время задержки между обращениеями к avtodispetcher.ru
	
	public function prepareAndParsing() {
		Service_Debug::setDebug();
		$objCity = new City();
		
		// Пишем в лог открытие нового сеанса
		if (false != ($fp = fopen('parse_log.txt', 'at'))) {
			$string =  "\n\n" . gmdate("Y-m-d H:i:s") . " ############# Новый сеанс. Количество маршрутов: " . $this->routeParcingLimit . " #############\n\n";
			fwrite($fp,$string);
		}
		
		// Массив всех городов из БД
		$this->currentCities = $objCity->find('all', array('select' => '`city`.`ID_city`,`city`.`city_name`,`city`.`latitude`,`city`.`longitude`,`city`.`city_type`', 'conditions' => "`city`.`no_on_map` = 0 AND `city`.`ID_city` < 209"));
		//$this->currentCities = $objCity->find('all', array('select' => '`city`.`ID_city`,`city`.`city_name`,`city`.`latitude`,`city`.`longitude`,`city`.`city_type`', 'conditions' => '`city`.`no_on_map` = 0 AND (`city`.`ID_city`=1 OR `city`.`ID_city`=2)'));

		$citiesCount = count($this->currentCities);
		
		for ($i = 0; $i < $citiesCount - 1; $i++) {
			
			for ($j = $i+1; $j < $citiesCount; $j++) {

				$objRoute = new Route();
				$IdOneCity = $this->currentCities[$i]['ID_city'];
				$IdAnotherCity = $this->currentCities[$j]['ID_city'];
				$oneCityName = $this->currentCities[$i]['city_name'];
				$anotherCityName = $this->currentCities[$j]['city_name'];
				
				if(!$objRoute->isAlreadyExists($IdOneCity,$IdAnotherCity)) {// Парсим маршрут и отмечаем его как отпарcенный (добавляем в route)
					$startParceTime = Pressure::calculateCurrentDuration();
					
					$parseResult = $this->parseCitiesAndDistances($oneCityName,$anotherCityName);
					switch($parseResult) {
						case 'OK' :
							$logString = 'Успешно спарсен маршрут ' . $oneCityName . ' - ' . $anotherCityName;
							break;
						case 'no_first' :
							$logString = 'АвтодиспетчерРУ не нашёл начальный город в маршруте ' . $oneCityName . ' - ' . $anotherCityName;
							break;
						case 'no_last' :
							$logString = 'АвтодиспетчерРУ не нашёл конечный город в маршруте ' . $oneCityName . ' - ' . $anotherCityName;
							break;
						case 'kaptcha' :
							$logString = 'АвтодиспетчерРУ остановил нас капчей на маршруте ' . $oneCityName . ' - ' . $anotherCityName;
							break;
						case 'many' :
							$logString = 'АвтодиспетчерРУ не принимает один из городов маршрута ' . $oneCityName . ' - ' . $anotherCityName . ', т.к. есть несколько таких городов. Надо уточнить запись в нашей БД';
							break;
						default:
							$logString = "Неведомая ошибка на маршруте " . $oneCityName . " - " . $anotherCityName;
							break;
					}
						
					// Пишем в лог
					$logString = Pressure::calculateCurrentDuration() . ' -------' . ($this->gettedRoutes + 1) . '------- ' . $logString;
					fwrite($fp, $logString . "\n");
					
					// Если сработала капча, то останавливаемся на 5 минут(иногда повторное обращение не требует капчу) и уменьшаем счётчик, чтоббы на следующем шаге 
					// вновь попытаться обработать этот маршрут, если капча сама не снимается, то скрипт будет пытаться обработать этот маршрут пока не сработает
					// ограничение по числу запросов на сеанс, либо пока вручную не зайдём на сайт и не введём капчу
					if($parseResult == 'kaptcha') {
					 sleep(300);
					 $j--;
					}
		
					// Увеличиваем число запрошенных маршрутов и если оно превышает лимит на сеанс - выходим
					$this->gettedRoutes++;
					if($this->gettedRoutes == $this->routeParcingLimit) break(2);
					
					// Смотрим разницу между временем работы парсера над текущим маршрутом и временем через которое следует обращаться к диспетчеру
					// Если разница меньше нуля, то нужно спать. Для больших маршрутов обработка может занять время большее чем $this->sleeptime - в таких случаях спать не надо
					$sleepingTime = Pressure::calculateCurrentDuration() - $startParceTime - $this->sleeptime;
					if($sleepingTime < 0) {
						$sleepingTime = rand(100,200*abs($sleepingTime)+200)/100;
						sleep($sleepingTime);
					}
				}
			}
		}
		
		if($this->gettedRoutes == 0) {
			$logString = Pressure::calculateCurrentDuration() . ' -------------- ' . "В БД нет городов для парсинга маршрутов.Требуется добавить новые \n";
			fwrite($fp, $logString);
		}
		elseif($this->gettedRoutes < $this->routeParcingLimit) {
			$logString = Pressure::calculateCurrentDuration() . ' -------------- ' . "Сеанс завершён досрочно, так как  для всех городов, находившихся в БД к началу сеанса, маршруты обработаны \n";
			fwrite($fp, $logString);
		}
		
		if ($fp) fclose($fp);
	}
	
	public function parseCitiesAndDistances($oneCity, $anotherCity) {	
		Service_Debug::setDebug();
		$objCity = new City();
		$objCurl = new Service_Curl();
		$url = 'http://www.avtodispetcher.ru/distance/';
		$objDB = Service_Database::instance();
		$arrParams = array(
				'start_city' => $oneCity,
				'end_city' => $anotherCity,
				'exclude_country' => '',
				'exclude_city' => '',
				'inter_city' => '',
				'road_checked1' => '1',
				'speed_road1' => '110',
				'road_checked2' => '1',
				'speed_road2' => '90',
				'road_checked3' => '1',
				'speed_road3' => '70',
				'road_checked4' => '1',
				'speed_road4' => '60',
				'fuel_charge' => '30',
				'fuel_price' => '20',
				'price_type' => 'time'
		);
		
		$result = $objCurl->httpRequest('GET', $url, $arrParams, false);
		// 1 блок Работаем с блоком js кода, который отрисовывает на карте маршрут.Получаем данные для таблицы городов: название, координаты
		$matchesCount = preg_match_all("#var pos[0-9]+\s=\snew\sgoogle.maps.LatLng\((-?[0-9]+\.[0-9]+),\s(-?[0-9]+\.[0-9]+)\);[\s\n]*.*title:\s\"(.*)\"#", $result, $arrMatches, PREG_PATTERN_ORDER);
		if($matchesCount) { // Если маршрут на диспетчере отрисовался
		
			$arrLatitudes = $arrMatches[1];	// Массив широт городов маршрута
			$arrLongitude = $arrMatches[2]; // Массив долгот городов маршрута
			$arrCityNamesJs = $arrMatches[3]; // Массив имён городов из js-кода
			
			// Ключами массива городов делаем названия городов
			$citiesInDB = Service_ArrayConverter::makeKeyArrayFromMultyArray($this->currentCities, 'city_name');
				
			// Перебираем массив городов полученный из js-кода
			for($i = 0; $i < $matchesCount; $i++) {
				if(!$objCity->getIdByName($arrCityNamesJs[$i])) { // Если в БД нет такого города, то добавляем
					$cityArray = array(
							'city_name' => $arrCityNamesJs[$i],
							'latitude' => $arrLatitudes[$i],
							'longitude' => $arrLongitude[$i]
					);
					
					$objCity = new City();
					$objCity->setFields($cityArray);
					$objCity->save();
				}
				// Иначе отправляем город на проверку присутсвтия координат и их добавления в противном случае
				else $objCity->updateCityCoordinates($arrCityNamesJs[$i], $arrLatitudes[$i], $arrLongitude[$i]);
			}
			
			// 2 блок Обрабатываем первый населенный пункт отдельно, так как для него нет информации о километраже, то маска для него будет отдельная
			if(preg_match("#class='distance_city([0-9]*)'></span>\s*(.*)\s*</td><td valign=top>#", $result, $arrMatch)) {
				$fristCityType = $arrMatch[1];
				$firstCityName = $arrMatch[2];
			}
			
			// 3 блок Работаем с блоком таблицей на сайте avtodispetcher.ru, где расписываются расстояния и типы дорог, также здесь указаны типы городов
			// под эту маску попадут все города маршрута кроме первого
			if(preg_match_all("#class=distance_road([0-9]*).*\s*.*class='distance_city([0-9]*)'></span>\s*(.*)\s*</td><td valign=top>.*class='hidden'>([0-9]*)\sкм</a></td><td><a#", $result, $arrMatches, PREG_PATTERN_ORDER)) {
				$arrRoadTypes = $arrMatches[1]; 			// Массив типов  дорог городов из таблички расстояний
				$arrCityTypes = $arrMatches[2];				// Массив типов городов из таблички расстояний
				$arrCityNamesHTML = $arrMatches[3];			// Массив имен городов из таблички расстояний
				$arrRoadDistances = $arrMatches[4];			// Массив расстояний до предыдщего города из таблички расстояний
				
				$distancesIdsArray = array();				// Массив для хранения всех отрезков, составляющих текущий маршрут
				// Сохранение отрезков маршрута, а также при необходимости сохранение городов,типов городов на отрезках
				for ($i = 0; $i < count($arrCityNamesHTML); $i++) { // Перебираем массив городов, взятый из таблички расстояний
					$objCity = new City();
					$objDistance = new Distance();
			
					if($i == 0) { // Первый город маршрута взят из БД в начале, поэтому его id есть в $citiesInDB - берём оттуда
						$idOneCity = $citiesInDB[$firstCityName]['ID_city'];
						// Если в БД нет типа первого города - ставим !!!
						if(!isset($this->$citiesInDB[$firstCityName]['city_type'])) $objCity->updateCityType($firstCityName,$fristCityType);
					}
					// Первая точка отрезка всегда существует в бд, т.к. она добавляется на предущем шаге(если ранее не было)
					else $idOneCity = $objCity->getIdByNameAndCityType($arrCityNamesHTML[$i-1], $arrCityTypes[$i-1]);
			
					// Города кроме первого и последнего могут не быть в БД (фишка автодиспетчера - некоторые города(дороги у которых нет конкретных координат) не отображается на карте,
					// тоесть не факт, что такой город мы сохранили в 1 блоке). Поэтому проверяем и если эта такой город сохраняем его с отметкой no_on_map:
					$idAnotherCity = $objCity->getIdByNameAndCityType($arrCityNamesHTML[$i], $arrCityTypes[$i]);
					if(!$idAnotherCity) {
						$objCity->setFields(array('city_name' => $arrCityNamesHTML[$i], 'city_type' => $arrCityTypes[$i], 'no_on_map' => 1));
						$objCity->save();
						$idAnotherCity = $objCity->id;
					}
					else $objCity->updateCityType($arrCityNamesHTML[$i],$arrCityTypes[$i]);
						
			
					$roadDistance = $arrRoadDistances[$i];
					$roadType = $arrRoadTypes[$i];
					
					$IdDistance = $objDistance->getDistanceId($idOneCity,$idAnotherCity);
					if(!$IdDistance) { // Если такого отрезка нет в БД
						$objDistance->setFields(array('ID_one_city' => $idOneCity, 'ID_another_city' => $idAnotherCity, 'distance' => $roadDistance, 'road_type' => $roadType));
						$objDistance->save();
						$distancesIdsArray[] = $objDistance->id;
					}
					else $distancesIdsArray[] = $IdDistance;
				}
				// блок 3.1 Отмечает все возможные наборы отрезков маршрута, как отпарсенные маршруты
				// Например маршрут Москва-Ухта-Спб-Минск. в route добавится Москва-Ухта, Москва-Спб, Москва-Минск, Ухта-Спб,Ухта-Минск,Спб-Минкс
				// Чтобы далее не обрабатывать эти маршруты, так как данные по ним уже есть.
				$citiesCount = count($arrCityNamesHTML);
				$objCity = new City();
				
				// Отдельно обрабатываем маршруты с первым городом, т.к. первого города нет в $arrCityNamesHTML
				for ($k = 0; $k < $citiesCount; $k++) {
					
					$objRoute = new Route();
					
					// Первый город маршрута взят из БД в начале, поэтому его id есть в $citiesInDB - берём оттуда
					$IdOneCity = $citiesInDB[$firstCityName]['ID_city'];
					$IdAnotherCity = $objCity->getIdByNameAndCityType($arrCityNamesHTML[$k],$arrCityTypes[$k]);
					
					
					if(!$objRoute->isAlreadyExists($IdOneCity,$IdAnotherCity))  {// отмечаем его как отпарcенный (добавляем в route)
						$objRoute->setFields(array('ID_one_city' => $IdOneCity, 'ID_another_city' => $IdAnotherCity));
						$objRoute->save();
						
						$objDistanceRoute = new DistanceRoute();
						// Сохраняем ссылки на отрезки для маршрутов-сочетаний с 1 городом
						for($z = 0; $z < $k + 1; $z++) {
							// Добавляет запись в sql-запрос
							$objDistanceRoute->addForInsert($objRoute->id, $distancesIdsArray[$z]);
						}
						// Добавляем все ссылки на отрезки для текущего маршрута
						$objDistanceRoute->insertCollection();
					}
				}
				
				// обрабатываем остальные города маршрута
				for ($k = 0; $k < $citiesCount - 1; $k++) {
				
					for ($e = $k+1; $e < $citiesCount; $e++) {
							
						$objRoute = new Route();
						$IdOneCity = $objCity->getIdByNameAndCityType($arrCityNamesHTML[$k],$arrCityTypes[$k]);
						$IdAnotherCity = $objCity->getIdByNameAndCityType($arrCityNamesHTML[$e],$arrCityTypes[$e]);
				
						if(!$objRoute->isAlreadyExists($IdOneCity,$IdAnotherCity))  {// отмечаем его как отпарcенный (добавляем в route)
								$objRoute->setFields(array('ID_one_city' => $IdOneCity, 'ID_another_city' => $IdAnotherCity));
								$objRoute->save();
								
								$objDistanceRoute = new DistanceRoute();
								// Сохраняем ссылки на отрезки для маршрутов-сочетаний остальных городов
								for($z = $k; $z < $e; $z++) {
									// Добавляет запись в sql-запрос
									$objDistanceRoute->addForInsert($objRoute->id, $distancesIdsArray[$z+1]);
								}
								// Добавляем все ссылки на отрезки для текущего маршрута !!! Можно вынести за главный цикл, но ошибка sql выдаёт. слишколм много записей в одном пакете тогда
								$objDistanceRoute->insertCollection();
						}
					}
				}	
			}
			return 'OK';
		}
		// Не найден начальный город
		if(preg_match("#<p>.*\s*не.*\s*начальный#", $result, $arrMatch)) $error = 'no_first';
		// Не найден конечный город
		elseif(preg_match("#<p>.*\s*не.*\s*конечный#", $result, $arrMatch)) $error = 'no_last';
		// Сработала капча
		elseif(preg_match("#введите\s*код:#", $result, $arrMatch)) $error = 'kaptcha';
		// Запрашиваемый нами город на автодиспетчере встречается несколько раз, надо поменять название до соответствия
		elseif(preg_match("#несколько\s*пунктов#", $result, $arrMatch)) $error = 'many';
		// Другие ошибки
		else $error = 'undefined';
		return $error;	
	}

}
?>